我正在学习如何创建 Chrome 扩展程序。我刚开始开发一个来捕捉 YouTube 事件。我想将它与 YouTube Flash 播放器一起使用(稍后我将尝试使其与 HTML5 兼容)。
清单.json:
{ "name": "MyExtension", "version": "1.0", "description": "Gotta catch Youtube events!", "permissions": ["tabs", "http://*/*"], "content_scripts" : [{ "matches" : [ "www.youtube.com/*"], "js" : ["myScript.js"] }] }
myScript.js:
function state() { console.log("State Changed!"); } var player = document.getElementById("movie_player"); player.addEventListener("onStateChange", "state"); console.log("Started!");
问题是控制台给了我 “开始!” ,但没有 “状态已更改!” 当我播放/暂停 YouTube 视频时。
将此代码放入控制台时,它可以工作。我究竟做错了什么?
根本原因: 内容脚本在“孤立世界”环境中执行。
解决方案:: 要 访问 页面 上下文(“主世界”)的函数/变量,您必须使用 DOM 将代码注入页面本身。如果您想 将 函数/变量公开给页面上下文(在您的情况下是state()方法),同样的事情。
state()
如果需要与页面脚本通信,请注意: 使用DOMCustomEvent处理程序。示例:一、二
CustomEvent
如果页面脚本中需要 API,请注意chrome: 由于chrome.*API 不能在页面脚本中使用,您必须在内容脚本中使用它们并通过 DOM 消息传递将结果发送到页面脚本(请参阅上面的注释)。
chrome
chrome.*
安全警告 : 页面可能会重新定义或扩充/挂钩内置原型,因此如果页面以不兼容的方式执行此操作,您公开的代码可能会失败。如果您想确保您的公开代码在安全的环境中运行,那么您应该a) 使用“run_at”:”document_start”声明您的内容脚本并使用方法 2-3 而不是 1,或者 b) 提取原始的原生内置 -通过一个空的 iframe 输入ins, 请注意,document_start您可能需要DOMContentLoaded在暴露的代码中使用事件来等待 DOM。
document_start
DOMContentLoaded
目前唯一兼容 ManifestV3 的方法。 当你有很多代码时特别好。将代码放在扩展名中的文件中,例如script.js.然后将其加载到您的内容脚本中,如下所示:
script.js
var s = document.createElement('script'); s.src = chrome.runtime.getURL('script.js'); s.onload = function() { this.remove(); }; (document.head || document.documentElement).appendChild(s);
js 文件必须暴露在web_accessible_resources:
web_accessible_resources
ManifestV2 的 manifest.json 示例
"web_accessible_resources": ["script.js"],
ManifestV3 的 manifest.json 示例
"web_accessible_resources": [{
“resources”: [“script.js”], “matches”: [““] }]
如果没有,控制台中会出现以下错误:
拒绝加载 chrome-extension://[EXTENSIONID]/script.js。资源必须列在 web_accessible_resources 清单键中,才能被扩展之外的页面加载。
当您想快速运行一小段代码时,此方法很有用。
var actualCode = `// Code here. // If you want to use a variable, use $ and curly braces. // For example, to use a fixed random number: var someFixedRandomValue = ${ Math.random() }; // NOTE: Do not insert unsafe variables in this way, see below // at "Dynamic values in the injected code" `; var script = document.createElement('script'); script.textContent = actualCode; (document.head||document.documentElement).appendChild(script); script.remove();
注意:模板文字仅在 Chrome 41 及更高版本中受支持。如果您希望扩展在 Chrome 40- 中运行,请使用:
var actualCode = ['/* Code here. Example: */' + 'alert(0);', '// Beware! This array have to be joined', '// using a newline. Otherwise, missing semicolons', '// or single-line comments (//) will mess up your', '// code ----->'].join('\n');
对于一大段代码,引用字符串是不可行的。可以使用函数而不是使用数组,并对其进行字符串化:
var actualCode = '(' + function() { // All code is executed in a local scope. // For example, the following does NOT overwrite the global `alert` method var alert = null; // To overwrite a global variable, prefix `window`: window.alert = null; } + ')();'; var script = document.createElement('script'); script.textContent = actualCode; (document.head||document.documentElement).appendChild(script); script.remove();
此方法有效,因为+字符串和函数上的运算符将所有对象转换为字符串。如果您打算多次使用代码,明智的做法是创建一个函数以避免代码重复。一个实现可能如下所示:
+
function injectScript(func) { var actualCode = '(' + func + ')();' ... } injectScript(function() { alert("Injected script"); });
注意:由于函数是序列化的,原来的作用域和所有绑定的属性都丢失了!
var scriptToInject = function() { console.log(typeof scriptToInject); }; injectScript(scriptToInject); // Console output: "undefined"
有时,您想立即运行一些代码,例如在<head>创建元素之前运行一些代码。这可以通过插入一个<script>标签来完成textContent(参见方法 2/2b)。
<head>
<script>
textContent
另一种 但不推荐 的方法是使用内联事件。不建议这样做,因为如果页面定义了禁止内联脚本的内容安全策略,则内联事件侦听器将被阻止。另一方面,由扩展注入的内联脚本仍然运行。如果您仍想使用内联事件,方法如下:
var actualCode = '// Some code example \n' + 'console.log(document.documentElement.outerHTML);'; document.documentElement.setAttribute('onreset', actualCode); document.documentElement.dispatchEvent(new CustomEvent('reset')); document.documentElement.removeAttribute('onreset');
注意:此方法假定没有其他全局事件侦听器处理该reset事件。如果有,您还可以选择其他全球事件之一。只需打开 JavaScript 控制台 (F12),键入document.documentElement.on,然后选择可用事件。
reset
document.documentElement.on
有时,您需要将任意变量传递给注入函数。例如:
var GREETING = "Hi, I'm "; var NAME = "Rob"; var scriptToInject = function() { alert(GREETING + NAME); };
要注入此代码,您需要将变量作为参数传递给匿名函数。一定要正确执行!以下将 不起作用 :
var scriptToInject = function (GREETING, NAME) { ... }; var actualCode = '(' + scriptToInject + ')(' + GREETING + ',' + NAME + ')'; // The previous will work for numbers and booleans, but not strings. // To see why, have a look at the resulting string: var actualCode = "(function(GREETING, NAME) {...})(Hi, I'm ,Rob)"; // ^^^^^^^^ ^^^ No string literals!
解决方案是JSON.stringify在传递参数之前使用。例子:
JSON.stringify
var actualCode = '(' + function(greeting, name) { ... } + ')(' + JSON.stringify(GREETING) + ',' + JSON.stringify(NAME) + ')';
如果你有很多变量,值得使用JSON.stringify一次,以提高可读性,如下:
... } + ')(' + JSON.stringify([arg1, arg2, arg3, arg4]) + ')';